import { Vector2 } from './Vector2';

export interface IRectangle
{
    /**
     * 矩形左上角的 x 坐标。
     * @default 0
     */
    x: number;

    /**
     * 矩形左上角的 y 坐标。
     * @default 0
     */
    y: number;

    /**
     * 矩形的宽度。
     * @default 0
     */
    width: number;

    /**
     * 矩形的高度。
     * @default 0
     */
    height: number;
}

/**
 * 矩形
 *
 * Rectangle 对象是按其位置（由它左上角的点 (x, y) 确定）以及宽度和高度定义的区域。<br/>
 * Rectangle 类的 x、y、width 和 height 属性相互独立；更改一个属性的值不会影响其他属性。
 * 但是，right 和 bottom 属性与这四个属性是整体相关的。例如，如果更改 right 属性的值，则 width
 * 属性的值将发生变化；如果更改 bottom 属性，则 height 属性的值将发生变化。
 */
export class Rectangle
{
    /**
     * 创建一个新 Rectangle 对象，其左上角由 x 和 y 参数指定，并具有指定的 width 和 height 参数。
     * @param x 矩形左上角的 x 坐标。
     * @param y 矩形左上角的 y 坐标。
     * @param width 矩形的宽度（以像素为单位）。
     * @param height 矩形的高度（以像素为单位）。
     */
    constructor(x = 0, y = 0, width = 0, height = 0)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    /**
     * 矩形左上角的 x 坐标。
     * @default 0
     */
    x: number;

    /**
     * 矩形左上角的 y 坐标。
     * @default 0
     */
    y: number;

    /**
     * 矩形的宽度（以像素为单位）。
     * @default 0
     */
    width: number;
    /**
     * 矩形的高度（以像素为单位）。
     * @default 0
     */
    height: number;

    /**
     * x 和 width 属性的和。
     */
    get right(): number
    {
        return this.x + this.width;
    }

    set right(value: number)
    {
        this.width = value - this.x;
    }

    /**
     * y 和 height 属性的和。
     */
    get bottom(): number
    {
        return this.y + this.height;
    }

    set bottom(value: number)
    {
        this.height = value - this.y;
    }

    /**
     * 矩形左上角的 x 坐标。更改 Rectangle 对象的 left 属性对 y 和 height 属性没有影响。但是，它会影响 width 属性，而更改 x 值不会影响 width 属性。
     * left 属性的值等于 x 属性的值。
     */
    get left(): number
    {
        return this.x;
    }

    set left(value: number)
    {
        this.width += this.x - value;
        this.x = value;
    }

    /**
     * 矩形左上角的 y 坐标。更改 Rectangle 对象的 top 属性对 x 和 width 属性没有影响。但是，它会影响 height 属性，而更改 y 值不会影响 height 属性。<br/>
     * top 属性的值等于 y 属性的值。
     */
    get top(): number
    {
        return this.y;
    }

    set top(value: number)
    {
        this.height += this.y - value;
        this.y = value;
    }

    /**
     * 由该点的 x 和 y 坐标确定的 Rectangle 对象左上角的位置。
     */
    get topLeft(): Vector2
    {
        return new Vector2(this.left, this.top);
    }

    set topLeft(value: Vector2)
    {
        this.top = value.y;
        this.left = value.x;
    }

    /**
     * 由 right 和 bottom 属性的值确定的 Rectangle 对象的右下角的位置。
     */
    get bottomRight(): Vector2
    {
        return new Vector2(this.right, this.bottom);
    }

    set bottomRight(value: Vector2)
    {
        this.bottom = value.y;
        this.right = value.x;
    }

    /**
     * 中心点
     */
    get center()
    {
        return new Vector2(this.x + (this.width / 2), this.y + (this.height / 2));
    }

    /**
     * 将源 Rectangle 对象中的所有矩形数据复制到调用方 Rectangle 对象中。
     * @param sourceRect 要从中复制数据的 Rectangle 对象。
     */
    copyFrom(sourceRect: IRectangle): Rectangle
    {
        this.x = sourceRect.x;
        this.y = sourceRect.y;
        this.width = sourceRect.width;
        this.height = sourceRect.height;

        return this;
    }

    /**
     * 将 Rectangle 的成员设置为指定值
     * @param x 矩形左上角的 x 坐标。
     * @param y 矩形左上角的 y 坐标。
     * @param width 矩形的宽度（以像素为单位）。
     * @param height 矩形的高度（以像素为单位）。
     */
    init(x: number, y: number, width: number, height: number): Rectangle
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;

        return this;
    }

    /**
     * 确定由此 Rectangle 对象定义的矩形区域内是否包含指定的点。
     * @param x 检测点的x轴
     * @param y 检测点的y轴
     * @returns 如果检测点位于矩形内，返回true，否则，返回false
     */
    contains(x: number, y: number): boolean
    {
        return this.x <= x
            && this.x + this.width >= x
            && this.y <= y
            && this.y + this.height >= y;
    }

    /**
     * 如果在 toIntersect 参数中指定的 Rectangle 对象与此 Rectangle 对象相交，则返回交集区域作为 Rectangle 对象。如果矩形不相交，
     * 则此方法返回一个空的 Rectangle 对象，其属性设置为 0。
     * @param toIntersect 要对照比较以查看其是否与此 Rectangle 对象相交的 Rectangle 对象。
     * @returns 等于交集区域的 Rectangle 对象。如果该矩形不相交，则此方法返回一个空的 Rectangle 对象；即，其 x、y、width 和
     * height 属性均设置为 0 的矩形。
     */
    intersection(toIntersect: Rectangle): Rectangle
    {
        if (!this.intersects(toIntersect))
        { return new Rectangle(); }

        const i: Rectangle = new Rectangle();

        if (this.x > toIntersect.x)
        {
            i.x = this.x;
            i.width = toIntersect.x - this.x + toIntersect.width;

            if (i.width > this.width)
            { i.width = this.width; }
        }
        else
        {
            i.x = toIntersect.x;
            i.width = this.x - toIntersect.x + this.width;

            if (i.width > toIntersect.width)
            { i.width = toIntersect.width; }
        }

        if (this.y > toIntersect.y)
        {
            i.y = this.y;
            i.height = toIntersect.y - this.y + toIntersect.height;

            if (i.height > this.height)
            { i.height = this.height; }
        }
        else
        {
            i.y = toIntersect.y;
            i.height = this.y - toIntersect.y + this.height;

            if (i.height > toIntersect.height)
            { i.height = toIntersect.height; }
        }

        return i;
    }

    /**
     * 按指定量增加 Rectangle 对象的大小（以像素为单位）
     * 保持 Rectangle 对象的中心点不变，使用 dx 值横向增加它的大小，使用 dy 值纵向增加它的大小。
     * @param dx Rectangle 对象横向增加的值。
     * @param dy Rectangle 对象纵向增加的值。
     */
    inflate(dx: number, dy: number): void
    {
        this.x -= dx;
        this.width += 2 * dx;
        this.y -= dy;
        this.height += 2 * dy;
    }

    /**
     * 确定在 toIntersect 参数中指定的对象是否与此 Rectangle 对象相交。此方法检查指定的 Rectangle
     * 对象的 x、y、width 和 height 属性，以查看它是否与此 Rectangle 对象相交。
     * @param toIntersect 要与此 Rectangle 对象比较的 Rectangle 对象。
     * @returns 如果两个矩形相交，返回true，否则返回false
     */
    intersects(toIntersect: Rectangle): boolean
    {
        return Math.max(this.x, toIntersect.x) <= Math.min(this.right, toIntersect.right)
            && Math.max(this.y, toIntersect.y) <= Math.min(this.bottom, toIntersect.bottom);
    }

    /**
     * 确定此 Rectangle 对象是否为空。
     * @returns 如果 Rectangle 对象的宽度或高度小于等于 0，则返回 true 值，否则返回 false。
     */
    isEmpty(): boolean
    {
        return this.width <= 0 || this.height <= 0;
    }

    /**
     * 将 Rectangle 对象的所有属性设置为 0。
     */
    setEmpty(): void
    {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }

    /**
     * 返回一个新的 Rectangle 对象，其 x、y、width 和 height 属性的值与原始 Rectangle 对象的对应值相同。
     * @returns 新的 Rectangle 对象，其 x、y、width 和 height 属性的值与原始 Rectangle 对象的对应值相同。
     */
    clone(): Rectangle
    {
        return new Rectangle(this.x, this.y, this.width, this.height);
    }

    /**
     * 确定由此 Rectangle 对象定义的矩形区域内是否包含指定的点。
     * 此方法与 Rectangle.contains() 方法类似，只不过它采用 Point 对象作为参数。
     * @param point 包含点对象
     * @returns 如果包含，返回true，否则返回false
     */
    containsPoint(point: Vector2): boolean
    {
        if (this.x < point.x
            && this.x + this.width > point.x
            && this.y < point.y
            && this.y + this.height > point.y)
        {
            return true;
        }

        return false;
    }

    /**
     * 确定此 Rectangle 对象内是否包含由 rect 参数指定的 Rectangle 对象。
     * 如果一个 Rectangle 对象完全在另一个 Rectangle 的边界内，我们说第二个 Rectangle 包含第一个 Rectangle。
     * @param rect 所检查的 Rectangle 对象
     * @returns 如果此 Rectangle 对象包含您指定的 Rectangle 对象，则返回 true 值，否则返回 false。
     */
    containsRect(rect: Rectangle): boolean
    {
        const r1 = rect.x + rect.width;
        const b1 = rect.y + rect.height;
        const r2 = this.x + this.width;
        const b2 = this.y + this.height;

        return (rect.x >= this.x) && (rect.x < r2) && (rect.y >= this.y) && (rect.y < b2) && (r1 > this.x) && (r1 <= r2) && (b1 > this.y) && (b1 <= b2);
    }

    /**
     * 确定在 toCompare 参数中指定的对象是否等于此 Rectangle 对象。
     * 此方法将某个对象的 x、y、width 和 height 属性与此 Rectangle 对象所对应的相同属性进行比较。
     * @param toCompare 要与此 Rectangle 对象进行比较的矩形。
     * @returns 如果对象具有与此 Rectangle 对象完全相同的 x、y、width 和 height 属性值，则返回 true 值，否则返回 false。
     */
    equals(toCompare: Rectangle): boolean
    {
        if (this === toCompare)
        {
            return true;
        }

        return this.x === toCompare.x && this.y === toCompare.y
            && this.width === toCompare.width && this.height === toCompare.height;
    }

    /**
     * 增加 Rectangle 对象的大小。此方法与 Rectangle.inflate() 方法类似，只不过它采用 Point 对象作为参数。
     */
    inflatePoint(point: Vector2): void
    {
        this.inflate(point.x, point.y);
    }

    /**
     * 按指定量调整 Rectangle 对象的位置（由其左上角确定）。
     * @param dx 将 Rectangle 对象的 x 值移动此数量。
     * @param dy 将 Rectangle 对象的 t 值移动此数量。
     */
    offset(dx: number, dy: number): void
    {
        this.x += dx;
        this.y += dy;
    }

    /**
     * 将 Point 对象用作参数来调整 Rectangle 对象的位置。此方法与 Rectangle.offset() 方法类似，只不过它采用 Point 对象作为参数。
     * @param point 要用于偏移此 Rectangle 对象的 Point 对象。
     */
    offsetPoint(point: Vector2): void
    {
        this.offset(point.x, point.y);
    }

    /**
     * 生成并返回一个字符串，该字符串列出 Rectangle 对象的水平位置和垂直位置以及高度和宽度。
     * @returns 一个字符串，它列出了 Rectangle 对象的下列各个属性的值：x、y、width 和 height。
     */
    toString(): string
    {
        return `(x=${this.x}, y=${this.y}, width=${this.width}, height=${this.height})`;
    }

    /**
     * 通过填充两个矩形之间的水平和垂直空间，将这两个矩形组合在一起以创建一个新的 Rectangle 对象。
     * @param toUnion 要添加到此 Rectangle 对象的 Rectangle 对象。
     * @returns 充当两个矩形的联合的新 Rectangle 对象。
     */
    union(toUnion: Rectangle): Rectangle
    {
        const result = this.clone();

        if (toUnion.isEmpty())
        {
            return result;
        }
        if (result.isEmpty())
        {
            result.copyFrom(toUnion);

            return result;
        }
        const l = Math.min(result.x, toUnion.x);
        const t = Math.min(result.y, toUnion.y);

        result.init(l, t,
            Math.max(result.right, toUnion.right) - l,
            Math.max(result.bottom, toUnion.bottom) - t);

        return result;
    }

    /**
     *
     * @param point 点
     * @param pout 输出点
     */
    clampPoint(point: Vector2, pout = new Vector2())
    {
        return pout.copy(point).clamp(this.topLeft, this.bottomRight);
    }

    /**
     * The size of the Rectangle object, expressed as a Point object with the
     * values of the <code>width</code> and <code>height</code> properties.
     */
    public get size(): Vector2
    {
        return new Vector2(this.width, this.height);
    }
}
